Developer --> Technical Publications
PATH  Mac OS X Server Documentation > Mac OS X Server Release Notes


[Back]

MacOS X Server Release Notes Copyright © 1998 by Apple Computer, Inc. All Rights Reserved.

 

MacOS X Server Developer Release Notes:
Thread Support in the Application Kit

This release note provides some guidelines for writing a threaded application. For a related discussion, see Thread-safe Classes in Foundation in the release notes for the Foundation framework.

 

General Guidelines

  1. In general, immutable objects are thread-safe. Once you create them, you can safely pass these objects to and from threads. On the other hand, mutable objects are not thread-safe. To use mutable objects in a threaded application, the application must synchronize appropriately (see "Foundation Collection Classes," below).

  2. To safely use the Application Kit in drawing operations involving multiple threads, you must ensure that all threads have their own NSDPSContext. (Use the convenience function [NSApplication detachDrawingThread:] to create a thread with its own graphics context).

  3. If a thread is going to draw in a view, bracket all drawing code between the NSView methods lockFocusIfCanDraw (see "NSView," below) and unlockFocus.

  4. The main thread of the application is responsible for handling events; the main thread is the one blocked in [NSApplication run] calling [NSApplication nextEventMatchingMask: ...] and [NSApplication sendEvent:]. While the Application Kit continues to work if other threads are involved in the event path, operations can occur out of sequence. For example if two different threads are responding to key events, the keys could be received out of order. By letting the main thread process events, you achieve a more consistent user experience. The main thread can send a message (DO, Mach message, or through a NSConditionLock) to get another thread to do some work.

  5. Multithreading doesn't come for free. Perceived performance might actually be improved due to deferred execution. However, raw performance on a single-processor system will be somewhat slower for multi-threaded applications. This is due to the overhead associated with proper synchronization (locking, messaging, and so on). Consider carefully whether you need to use threads. Many problems can be solved by deferring execution using timers (NSTimer).

Note: The Java Yellow Box API implementation (including AWT) is multi-threaded.

The major reward from multi-threading your application is improved performance on a multi-processor system.

 

Subsystem Notes

NSDPSContext

Only one thread can use an NSDPSContext at a time. A thread can use multiple NSDPSContexts during its lifetime, but if two threads access a single NSDPSContext at the same time, unpredictable results can occur.

Flushing still occurs automatically after every event in the main thread of the application. However, there are times when you need to explicitly flush when drawing on other threads. Use the flush: and flushGraphics: methods of NSDPSContext to flush the stream buffer and window backing store, respectively.

 

NSImage

One thread can create an image, draw it, pass it off to another thread, which can then draw it, and so forth. The underlying image cache is shared among all threads.

 

NSView

You can create, destroy, resize, move, and perform other operations with NSViews from different threads. The system ensures that all drawing is deferred while these operations occur (through the lockFocusIfCanDraw and unlockFocus mentioned above). lockFocusIfCanDraw ensures that lockFocus and canDraw methods are called atomically. Subthread drawing, now, should look like the code fragment below.

if ([aView lockFocusIfCanDraw]) {

    /* Drawing operations */

    [aView unlockFocus];

}

In a multi-threaded application, the main thread is still responsible for redisplaying dirty views through the same process as a single-threaded application. The drawRect: method of every dirty view is called in the main thread. If the drawing needs to be done on another thread, the drawRect: method for the view should arrange for the secondary thread to do the drawing and not do any drawing in drawRect:.

If a secondary thread of an application wants to cause portions of the view to be redrawn on the main thread, the normal mechanisms work (setNeedsDisplay:, setNeedsDisplayInRect:, and setViewsNeedDisplay:).

Performance Note: The view system's gstates are per-thread. Using gstates in a multi-threaded application improves drawing performance in a way similar to a single-threaded application. Please use the public method (gstate) to get at the gstate of the current thread.

 

Foundation Collection Classes

The collection classes in Foundation (for example, NSMutableArray, NSMutableDictionary) are not thread-safe. You must lock around them to use them in a multi-threaded application:

Not Thread-Safe:
thread-1 thread-2

count = [array count];

for (i = 0; i < count; i++) { 

    [array addObject:newObject];

    if ([array objectAtIndex:i] == obj) {

        // ... do something ...

    }

}

In this example, if thread-1 were preempted while it iterated over the array, the second thread, thread-2 , could be scheduled and add an object to the array. When the first thread, thread-1, ran again, it's count variable would be stale, and it may not find the desired object.

Thread-Safe :
thread-1 thread-2

[arrayLock lock];

count = [array count];

for (i = 0; i < count; i++) { 

    if ([array objectAtIndex:i] == obj) { 

        [array addObject:newObject];

        // ... do something ...  

    }

}

[arrayLock unlock];

This is the same example using locks around the array manipulation. The first thread will complete its entire iteration before the second thread can access the array.

Note: See Foundation release notes for more information on Foundation thread support.

 

Known Problems and Limitations

Thread termination can cause some drawing problems. In general, the Application Kit doesn't know if your application is using threads to draw or to do similar operations. Before dismissing a window, you should ensure that your threads have stopped drawing.

Using locks within drawRect: can cause deadlock problems if they are held beyond the drawRect:. It is safe to lock and unlock NSLocks within a drawRect:.

 

Changes since DR2

You can now create a window on a secondary thread.  The AppKit ensures that the data structures associated with a window are deallocated on the main thread in order to avoid race conditions.  There is some possibility that window objects may leak in an application that deals with a lot of windows concurrently.  There is also a theoretical limit on the number of windows that can be displayed at the same time.

You can now call [NSApplication postEvent:atStart:] from a secondary thread to post an event to the main thread's event queue.  Order is not guaranteed with respect to user input events, however.  The main thread of the application is still responsible for handling events in the event queue.

An explicit call to [NSView display] on a secondary thread will no longer cause recursive display on that thread.  The call to display will be treated as if it were a call to [NSView setNeedsDisplay:YES], and the recursive display will be done on the main thread.

You can now create a modal window on a secondary thread. The AppKit blocks the calling secondary thread while the main thread is running the modal loop.